#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

#[frame_support::pallet]
pub mod pallet {
    use codec::{Decode, Encode};
    use frame_support::{
        decl_error, decl_event, decl_module, decl_storage, dispatch, ensure, traits::Randomness,
        StorageMap, StorageValue,
    };
    use sp_runtime::traits::{Bounded, Hash};
    use sp_std::prelude::Vec;

    use frame_system::pallet_prelude::*;

    use frame_system as system;
    use pallet_balances as balances;
    use pallet_randomness_collective_flip as randomness_collective_flip;

    #[derive(Encode, Decode, Clone, PartialEq)]
    #[cfg_attr(feature = "std", derive(Debug))]
    pub struct Token<Hash, Balance> {
        pub token_hash: Hash,
        pub symbol: Vec<u8>,
        pub total_supply: Balance,
        pub ttype: TokenType,
    }

    #[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, Eq)]
    pub enum TokenType {
        Normal,
        Liquidity,
    }

    #[pallet::config]
    /// Configure the pallet by specifying the parameters and types on which it depends.
    pub trait Config<I:'static = ()>: balances::Config+frame_system::Config {
        /// Because this pallet emits events, it depends on the runtime's definition of an event.
        /// The overarching event type.
		type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
    }

    decl_error! {
        /// Error for the token module.
        pub enum Error for Module<T: Trait> {
            /// There is no match token
            NoMatchingToken,
            /// The balance is not enough
            BalanceNotEnough,
            /// Amount overflow
            AmountOverflow,
            /// Sender does not have token
            SenderHaveNoToken,
            /// Memo length exceed limitation
            MemoLengthExceedLimitation,
        }
    }

    decl_event!(
        pub enum Event<T>
        where
            <T as system::Trait>::AccountId,
            <T as system::Trait>::Hash,
            <T as balances::Trait>::Balance,
        {
            Issued(AccountId, Hash, Balance),
            Transferd(AccountId, AccountId, Hash, Balance),
            Freezed(AccountId, Hash, Balance),
            UnFreezed(AccountId, Hash, Balance),
        }
    );

    decl_storage! {
        trait Store for Module<T: Trait> as TokenModule {
            Tokens get(fn token): map hasher(blake2_128_concat) T::Hash => Option<Token<T::Hash, T::Balance>>;
            Owners get(fn owner): map hasher(blake2_128_concat) T::Hash => Option<T::AccountId>;

            BalanceOf get(fn balance_of): map hasher(blake2_128_concat) (T::AccountId, T::Hash) => T::Balance;
            FreeBalanceOf get(fn free_balance_of): map hasher(blake2_128_concat) (T::AccountId, T::Hash) => T::Balance;
            FreezedBalanceOf get(fn freezed_balance_of): map hasher(blake2_128_concat) (T::AccountId, T::Hash) => T::Balance;

            /// Index => TokenHash
            TokenHashByIndex get(fn token_hash_by_index): map hasher(blake2_128_concat) u64 => Option<T::Hash>;
            /// Index of tokens
            TokenIndex get(fn token_index): u64;

            Nonce get(fn nonce): u64;
        }
    }

    decl_module! {
        pub struct Module<T: Trait> for enum Call where origin: T::Origin {
            fn deposit_event() = default;

            type Error = Error<T>;

            #[weight = 200_000]
            pub fn issue(origin, symbol: Vec<u8>, total_supply: T::Balance) -> dispatch::DispatchResult {
                let sender = ensure_signed(origin)?;

                let hash = Self::do_issue(sender.clone(), symbol, total_supply, TokenType::Normal)?;
                Self::deposit_event(RawEvent::Issued(sender, hash.clone(), total_supply));

                Ok(())
            }

            #[weight = 200_000]
            pub fn transfer(origin, token_hash: T::Hash, to: T::AccountId, amount: T::Balance, memo: Option<Vec<u8>>)
                -> dispatch::DispatchResult {
                let sender = ensure_signed(origin)?;

                Self::do_transfer(sender.clone(), to.clone(), token_hash, amount, memo)?;
                Self::deposit_event(RawEvent::Transferd(sender, to, token_hash, amount));

                Ok(())
            }
        }
    }

    impl<T: Trait> Module<T> {
        pub fn do_issue(
            sender: T::AccountId,
            symbol: Vec<u8>,
            total_supply: T::Balance,
            ttype: TokenType,
        ) -> Result<T::Hash, dispatch::DispatchError> {
            let nonce = Nonce::get();

            let random_seed = <randomness_collective_flip::Module<T>>::random_seed();
            let hash = (random_seed, sender.clone(), nonce)
                .using_encoded(<T as system::Trait>::Hashing::hash);

            let token = Token::<T::Hash, T::Balance> {
                token_hash: hash.clone(),
                total_supply,
                symbol: symbol.clone(),
                ttype,
            };

            Nonce::mutate(|n| *n += 1);
            Tokens::<T>::insert(hash.clone(), token);
            Owners::<T>::insert(hash.clone(), sender.clone());
            BalanceOf::<T>::insert((sender.clone(), hash.clone()), total_supply);
            FreeBalanceOf::<T>::insert((sender.clone(), hash.clone()), total_supply);

            let index = Self::token_index();
            TokenHashByIndex::<T>::insert(index, hash);
            TokenIndex::mutate(|n| *n += 1);

            Ok(hash)
        }

        pub fn do_transfer(
            sender: T::AccountId,
            to: T::AccountId,
            hash: T::Hash,
            amount: T::Balance,
            memo: Option<Vec<u8>>,
        ) -> dispatch::DispatchResult {
            let token = Self::token(hash);
            ensure!(token.is_some(), Error::<T>::NoMatchingToken);

            if let Some(memo) = memo {
                ensure!(memo.len() <= 512, Error::<T>::MemoLengthExceedLimitation);
            }

            ensure!(
                <FreeBalanceOf<T>>::contains_key((sender.clone(), hash)),
                Error::<T>::SenderHaveNoToken
            );

            let from_amount = Self::balance_of((sender.clone(), hash.clone()));
            ensure!(from_amount >= amount, Error::<T>::BalanceNotEnough);
            let new_from_amount = from_amount - amount;

            let from_free_amount = Self::free_balance_of((sender.clone(), hash.clone()));
            ensure!(from_free_amount >= amount, Error::<T>::BalanceNotEnough);
            let new_from_free_amount = from_free_amount - amount;

            let to_amount = Self::balance_of((to.clone(), hash.clone()));
            let new_to_amount = to_amount + amount;
            ensure!(
                new_to_amount <= T::Balance::max_value(),
                Error::<T>::AmountOverflow
            );

            let to_free_amount = Self::free_balance_of((to.clone(), hash.clone()));
            let new_to_free_amount = to_free_amount + amount;
            ensure!(
                new_to_free_amount <= T::Balance::max_value(),
                Error::<T>::AmountOverflow
            );

            BalanceOf::<T>::insert((sender.clone(), hash.clone()), new_from_amount);
            FreeBalanceOf::<T>::insert((sender.clone(), hash.clone()), new_from_free_amount);
            BalanceOf::<T>::insert((to.clone(), hash.clone()), new_to_amount);
            FreeBalanceOf::<T>::insert((to.clone(), hash.clone()), new_to_free_amount);

            Ok(())
        }

        pub fn do_freeze(
            sender: T::AccountId,
            hash: T::Hash,
            amount: T::Balance,
        ) -> dispatch::DispatchResult {
            let token = Self::token(hash);
            ensure!(token.is_some(), Error::<T>::NoMatchingToken);

            ensure!(
                FreeBalanceOf::<T>::contains_key((sender.clone(), hash)),
                Error::<T>::SenderHaveNoToken
            );

            let old_free_amount = Self::free_balance_of((sender.clone(), hash.clone()));
            ensure!(old_free_amount >= amount, Error::<T>::BalanceNotEnough);

            let old_freezed_amount = Self::freezed_balance_of((sender.clone(), hash.clone()));
            ensure!(
                old_freezed_amount + amount <= T::Balance::max_value(),
                Error::<T>::AmountOverflow
            );

            FreeBalanceOf::<T>::insert((sender.clone(), hash.clone()), old_free_amount - amount);
            FreezedBalanceOf::<T>::insert(
                (sender.clone(), hash.clone()),
                old_freezed_amount + amount,
            );

            Self::deposit_event(RawEvent::Freezed(sender, hash, amount));

            Ok(())
        }

        pub fn do_unfreeze(
            sender: T::AccountId,
            hash: T::Hash,
            amount: T::Balance,
        ) -> dispatch::DispatchResult {
            let token = Self::token(hash);
            ensure!(token.is_some(), Error::<T>::NoMatchingToken);

            ensure!(
                FreeBalanceOf::<T>::contains_key((sender.clone(), hash)),
                Error::<T>::SenderHaveNoToken
            );

            let old_freezed_amount = Self::freezed_balance_of((sender.clone(), hash.clone()));
            ensure!(old_freezed_amount >= amount, Error::<T>::BalanceNotEnough);

            let old_free_amount = Self::free_balance_of((sender.clone(), hash.clone()));
            ensure!(
                old_free_amount + amount <= T::Balance::max_value(),
                Error::<T>::AmountOverflow
            );

            FreeBalanceOf::<T>::insert((sender.clone(), hash.clone()), old_free_amount + amount);
            FreezedBalanceOf::<T>::insert(
                (sender.clone(), hash.clone()),
                old_freezed_amount - amount,
            );

            Self::deposit_event(RawEvent::UnFreezed(sender, hash, amount));

            Ok(())
        }

        pub fn ensure_free_balance(
            owner: T::AccountId,
            hash: T::Hash,
            amount: T::Balance,
        ) -> dispatch::DispatchResult {
            let token = Self::token(hash);
            ensure!(token.is_some(), Error::<T>::NoMatchingToken);

            ensure!(
                FreeBalanceOf::<T>::contains_key((owner.clone(), hash.clone())),
                Error::<T>::SenderHaveNoToken
            );

            let free_amount = Self::free_balance_of((owner.clone(), hash.clone()));
            ensure!(free_amount >= amount, Error::<T>::BalanceNotEnough);

            Ok(())
        }
    }
}
